package com.pimee.support.shiro.config;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.DelegatingFilterProxy;

import com.pimee.common.redis.properties.RedisProperties;
import com.pimee.support.shiro.credentials.RetryLimitCredentialsMatcher;
import com.pimee.support.shiro.filter.LogoutFilter;
import com.pimee.support.shiro.filter.OnlineSessionFilter;
import com.pimee.support.shiro.filter.SyncOnlineSessionFilter;
import com.pimee.support.shiro.listener.ShiroSessionListener;
import com.pimee.support.shiro.realm.AdminShiroRealm;
import com.pimee.support.shiro.security.KickoutSessionControlFilter;
import com.pimee.support.shiro.security.UserModularRealmAuthenticator;
import com.pimee.support.shiro.session.OnlineSessionFactory;
import com.pimee.support.shiro.session.PmRedisSessionDAO;
import com.pimee.support.shiro.session.web.OnlineWebSessionManager;
import com.pimee.support.shiro.session.web.SpringSessionValidationScheduler;

@Configuration
@Order(-1) // 注解表示加载顺序
public class ShiroConfig {

	// 登录地址
	@Value("${shiro.user.loginUrl}")
	private String loginUrl;

	@Autowired
	private RedisProperties redisProperties;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Bean
	public FilterRegistrationBean delegatingFilterProxy() {
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
		DelegatingFilterProxy proxy = new DelegatingFilterProxy();
		proxy.setTargetFilterLifecycle(true);
		proxy.setTargetBeanName("shiroFilter");
		filterRegistrationBean.setFilter(proxy);
		return filterRegistrationBean;
	}

	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 没有登陆的用户只能访问登陆页面
		shiroFilterFactoryBean.setLoginUrl("/admin/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/admin/index");
		// 未授权界面; ----这个配置了没卵用，具体原因想深入了解的可以自行百度
		// shiroFilterFactoryBean.setUnauthorizedUrl("/auth/403");
		// 自定义拦截器
		Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
		// 限制同一帐号同时在线的个数。
		filtersMap.put("kickout", kickoutSessionControlFilter());
		filtersMap.put("logout", logoutFilter());
		filtersMap.put("onlineSession", onlineSessionFilter());
		filtersMap.put("syncOnlineSession", syncOnlineSessionFilter());
		shiroFilterFactoryBean.setFilters(filtersMap);
		// 权限控制map.
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		// 对静态资源设置匿名访问
		filterChainDefinitionMap.put("/favicon.ico**", "anon");
		filterChainDefinitionMap.put("/ruoyi.png**", "anon");
		filterChainDefinitionMap.put("/css/**", "anon");
		filterChainDefinitionMap.put("/docs/**", "anon");
		filterChainDefinitionMap.put("/fonts/**", "anon");
		filterChainDefinitionMap.put("/img/**", "anon");
		filterChainDefinitionMap.put("/ajax/**", "anon");
		filterChainDefinitionMap.put("/js/**", "anon");
		filterChainDefinitionMap.put("/ruoyi/**", "anon");
		filterChainDefinitionMap.put("/wx/api/**", "anon");
		filterChainDefinitionMap.put("/QRIDX/**", "anon");
		// filterChainDefinitionMap.put("/a/**", "anon");
		filterChainDefinitionMap.put("/ali/api/**", "anon");
		filterChainDefinitionMap.put("/call/api/**", "anon");
		filterChainDefinitionMap.put("/api/websocket/**", "anon");
		filterChainDefinitionMap.put("/druid/**", "anon");
		filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
		// 不需要拦截的访问
		filterChainDefinitionMap.put("/admin/login", "anon");
		filterChainDefinitionMap.put("/admin/logout", "logout");
		filterChainDefinitionMap.put("/auth/kickout", "anon");
		filterChainDefinitionMap.put("/**", "authc,onlineSession,syncOnlineSession");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	@Bean(name = "securityManager")
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

		// 设置realm.
		securityManager.setAuthenticator(modularRealmAuthenticator());
		List<Realm> realms = new ArrayList<>();
		// 添加多个Realm
		realms.add(adminShiroRealm());
		securityManager.setRealms(realms);

		// 设置realm.
		// securityManager.setRealm(AdminAdminShiroRealm());
		// 自定义缓存实现 使用redis
		securityManager.setCacheManager(cacheManager());
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		return securityManager;
	}

	/**
	 * 身份认证realm; (这个需要自己写，账号密码校验；权限等)
	 *
	 * @return
	 */
	@Bean
	public AdminShiroRealm adminShiroRealm() {
		AdminShiroRealm adminShiroRealm = new AdminShiroRealm();
		// 匹配器，credentialsMatcher使用RetryLimitCredentialsMatcher
		// hashedCredentialsMatcher使用HashedCredentialsMatcher
		// 这里简洁可以使用hashedCredentialsMatcher
		// hopeShiroReam.setCredentialsMatcher(hashedCredentialsMatcher());
		adminShiroRealm.setCredentialsMatcher(credentialsMatcher());
		return adminShiroRealm;
	}

	/***
	 * 凭证匹配器
	 * 
	 * @return
	 */
	@Bean(name = "credentialsMatcher")
	public RetryLimitCredentialsMatcher credentialsMatcher() {
		return new RetryLimitCredentialsMatcher();
	}

	/**
	 * 退出过滤器
	 */
	public LogoutFilter logoutFilter() {
		LogoutFilter logoutFilter = new LogoutFilter();
		logoutFilter.setLoginUrl(loginUrl);
		return logoutFilter;
	}

	/**
	 * 自定义在线用户处理过滤器
	 */
	@Bean
	public OnlineSessionFilter onlineSessionFilter() {
		OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
		onlineSessionFilter.setLoginUrl(loginUrl);
		return onlineSessionFilter;
	}

	/**
	 * 自定义在线用户同步过滤器
	 */
	@Bean
	public SyncOnlineSessionFilter syncOnlineSessionFilter() {
		SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
		return syncOnlineSessionFilter;
	}

	/**
	 * cacheManager 缓存 redis实现 使用的是shiro-redis开源插件
	 *
	 * @return
	 */
	public RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setPrincipalIdFieldName("userId");
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;

	}

	/**
	 * 配置shiro redisManager 使用的是shiro-redis开源插件
	 *
	 * @return
	 */
	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		redisManager.setHost(redisProperties.getHost() + ":" + redisProperties.getPort());
		// redisManager.setExpire(1800);// 配置缓存过期时间
		redisManager.setTimeout(redisProperties.getTimeout());
		// redisManager.setPassword(password);
		return redisManager;
	}

	/**
	 * Session Manager 使用的是shiro-redis开源插件
	 */
	// @Bean
	// public DefaultWebSessionManager sessionManager() {
	// DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
	// sessionManager.setSessionDAO(redisSessionDAO());
	//
	// // 配置session监听
	// Collection<SessionListener> listeners = new ArrayList<>();
	// listeners.add(sessionListener());
	// sessionManager.setSessionListeners(listeners);
	//
	// // 全局会话超时时间（单位毫秒），默认30分钟 暂时设置为10秒钟 用来测试
	// sessionManager.setGlobalSessionTimeout(1800000);
	// // 是否开启删除无效的session对象 默认为true
	// sessionManager.setDeleteInvalidSessions(true);
	// // 是否开启定时调度器进行检测过期session 默认为true
	// sessionManager.setSessionValidationSchedulerEnabled(true);
	//
	// sessionManager.setSessionFactory(sessionFactory());
	//
	// return sessionManager;
	// }

	/**
	 * 会话管理器
	 */
	@Bean
	public OnlineWebSessionManager sessionManager() {
		OnlineWebSessionManager manager = new OnlineWebSessionManager();
		// 加入缓存管理器
		manager.setCacheManager(cacheManager());
		// 删除过期的session
		manager.setDeleteInvalidSessions(true);
		// 设置全局session超时时间
		manager.setGlobalSessionTimeout(1800000);
		// 去掉 JSESSIONID
		manager.setSessionIdUrlRewritingEnabled(false);
		// 定义要使用的无效的Session定时调度器
//		manager.setSessionValidationScheduler(sessionValidationScheduler());
		// 是否定时检查session
		manager.setSessionValidationSchedulerEnabled(true);
		// 自定义SessionDao
		manager.setSessionDAO(redisSessionDAO());
		// 自定义sessionFactory
		manager.setSessionFactory(sessionFactory());
		return manager;
	}

	/**
	 * RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件
	 */
	@Bean
	public PmRedisSessionDAO redisSessionDAO() {
		PmRedisSessionDAO redisSessionDAO = new PmRedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		// redisSessionDAO.setExpireTime(30 * 60 * 1000);
		return redisSessionDAO;
	}

	/**
	 * 自定义sessionFactory会话
	 */
	@Bean
	public OnlineSessionFactory sessionFactory() {
		OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
		return sessionFactory;
	}

	/**
	 * 限制同一账号登录同时登录人数控制
	 *
	 * @return
	 */
	@Bean
	public KickoutSessionControlFilter kickoutSessionControlFilter() {
		KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
		// 使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
		kickoutSessionControlFilter.setCacheManager(cacheManager());
		// 用于根据会话ID，获取会话进行踢出操作的；
		kickoutSessionControlFilter.setSessionManager(sessionManager());
		// 是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；
		kickoutSessionControlFilter.setKickoutAfter(false);
		// 同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
		kickoutSessionControlFilter.setMaxSession(1);
		kickoutSessionControlFilter.setKickoutUrl("/auth/kickout");
		return kickoutSessionControlFilter;
	}

	/**
	 * 自定义sessionFactory调度器
	 */
	@Bean
	public SpringSessionValidationScheduler sessionValidationScheduler() {
		SpringSessionValidationScheduler sessionValidationScheduler = new SpringSessionValidationScheduler();
		// 相隔多久检查一次session的有效性，单位毫秒，默认就是10分钟
		sessionValidationScheduler.setSessionValidationInterval(10 * 60 * 1000);
		// 设置会话验证调度器进行会话验证时的会话管理器
		sessionValidationScheduler.setSessionManager(sessionValidationManager());
		return sessionValidationScheduler;
	}

	/**
	 * 会话管理器
	 */
	@Bean
	public OnlineWebSessionManager sessionValidationManager() {
		OnlineWebSessionManager manager = new OnlineWebSessionManager();
		// 加入缓存管理器
		manager.setCacheManager(cacheManager());
		// 删除过期的session
		manager.setDeleteInvalidSessions(true);
		// 设置全局session超时时间
		manager.setGlobalSessionTimeout(180 * 60 * 1000);
		// 去掉 JSESSIONID
		manager.setSessionIdUrlRewritingEnabled(false);
		// 是否定时检查session
		manager.setSessionValidationSchedulerEnabled(true);
		// 自定义SessionDao
		manager.setSessionDAO(redisSessionDAO());
		// 自定义sessionFactory
		manager.setSessionFactory(sessionFactory());
		return manager;
	}

	/**
	 * 配置session监听
	 * 
	 * @return
	 */
	@Bean("sessionListener")
	public ShiroSessionListener sessionListener() {
		ShiroSessionListener sessionListener = new ShiroSessionListener();
		return sessionListener;
	}

	/**
	 * 解决spring-boot Whitelabel Error Page
	 * 
	 * @return
	 *//*
		 * @Bean public EmbeddedServletContainerCustomizer containerCustomizer() {
		 * return new EmbeddedServletContainerCustomizer() {
		 * 
		 * @Override public void customize(ConfigurableEmbeddedServletContainer
		 * container) { ErrorPage error401Page = new
		 * ErrorPage(HttpStatus.HTTP_UNAUTHORIZED, "/unauthorized.html"); ErrorPage
		 * error404Page = new ErrorPage(HttpStatus.HTTP_NOT_FOUND, "/404.html");
		 * ErrorPage error500Page = new ErrorPage(HttpStatus.HTTP_INTERNAL_ERROR,
		 * "/500.html"); container.addErrorPages(error401Page, error404Page,
		 * error500Page); } }; }
		 */

	/***
	 * 授权所用配置
	 *
	 * @return
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

	/***
	 * 使授权注解起作用不如不想配置可以在pom文件中加入 <dependency>
	 * <groupId>org.springframework.boot</groupId>
	 * <artifactId>spring-boot-starter-aop</artifactId> </dependency>
	 * 
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/***
	 * 管理shirobean生命周期
	 * 为什么会有static，详情查看：https://blog.csdn.net/u013821237/article/details/82868206
	 * 
	 * @return
	 */
	@Bean
	public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 系统自带的Realm管理，主要针对多realm
	 */
	@Bean
	public ModularRealmAuthenticator modularRealmAuthenticator() {
		// 自己重写的ModularRealmAuthenticator
		UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
		modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
		return modularRealmAuthenticator;
	}

}